iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0

前面已經介紹過各類資料操作框架的特色之後,這邊來介紹其中我比較熟悉也蠻多人使用的 Spring Data Jpa,雖然比較不用寫 SQL 所以可能會對於 SQL 掌握比較少,但我覺得透過物件的方式來操作資料也是本身 Java 的特色之一,搭配 Spring Boot 能夠整合在框架中使用是很方便的

使用前概念

根據常用開發架構的 MVC 架構,資料庫的部分主要是由 Model 層部分進行處理,這邊對應在 Spring Boot 的開發上會放在 Dao 和 Service,Dao 主要是定義我們與資料庫的溝通連結,在 Spring Data Jpa 主要就是繼承 JpaRepository 去處理資料的存取,最後將拿到得資料返回給 Service 進行業務邏輯的操作或是資料的組裝,最後回給 Controller。

引入套件

pom.xml 加入下面套件,spring-boot-starter-data-jpa 就是我們要使用的 JPA 套件,另外一個則是mysql 連線用的套件,可以根據使用不同資料庫做更換

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>    
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

配置 application properties

這部分根據你使用的資料庫不同會有一些差異,以下拿 Mysql 為例,主要需要配置概念都差不多,就是配置你的資料庫 Driver, 資料來源, 使用者名稱, 密碼等等

# 基本來源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/{databaseName}
spring.datasource.username={username}
spring.datasource.password={userPassword}

# 額外配置
# 資料表初始化及自動更新
spring.jpa.hibernate.ddl-auto=update

# 是否在 console 印出 SQL 指令並對其格式化
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

假設我們想要設計產品 Product 的資料操作,會定義他的實體類別 (Entity),來對應到資料庫的 products 表,當我們進行資料都是用 Product 這個物件來操作資料的存取,查詢就將資料轉成我們定義好的物件回給我們,儲存、修改、刪除等等的操作也是由此物件去進行。

資料庫建立及資料表建立

資料庫建立部分大家可以自行找自己所使用的用法,這邊就不說明,提供一個簡單通用的 SQL 例子方便沒使用過的可以有環境進行操作

CREATE TABLE products (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    description TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

Entity

新增一個 class,定義我們要作為資料庫操作物件,會對應到資料表的個別欄位

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private Double price;
    private String description;

    // Getter and Setter...
}

@Entity 告訴 Jpa 它是要儲存到資料庫的實體類別

@Table 則設定它在資料庫中,要對應到叫做「products」的 table 。

@Id 表示 table 中的主鍵(Primary Key,PK)

@GernatedValue() 定義了主鍵值的產生方式。設定的方式 strategy = GenerationType.IDENTITY 就是表示主鍵會自動增長。

後面章節會講到一些 Lombok 註解的應用,可以讓 Spring Data Jpa 使用上更加簡潔方便,但希望各位使用時還是要清楚知道每個註解的由來跟原始作法

Dao 層繼承 JpaRepository

新增 Repository 來做為資料庫溝通介面

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    Product findByName(String name);
}

Respository 就是屬於 Dao 的部分,這邊會新增一個介面( interface) 來繼承 JpaRepository,就可以將 JPA 對於物件實體的映射包含資料庫的連線等等實現,後面<> 內需要放入你要操作的物件實體及他內部主鍵型別 (id 的型別) 最後會是像這樣 JpaRepository<Entity, 主鍵型別>,下面可以加入任何你想要使用的方法來操作資料庫,通常預設已經有基本可以針對 id 搜尋單筆,或是直接查詢整筆,就不用宣告出來,透過繼承就可以後續直接再 Service 層中使用。

Service 層中用慣例優於配置來進行 CRUD 操作

新增 Service 層應用 JPA Repository 定義好的或是預設的方法操作資料

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    // 新增產品
    public Product createProduct(Product product) {
        return productRepository.save(product);
    }

    // 根據 id 查詢單一產品
    public Optional<Product> getProductById(Long id) {
        return productRepository.findById(id);
    }
   
    // 根據名稱查詢單一產品
    public Product getProductByName(String name) {
        return productRepository.findByName(name);
    }

    // 查詢所有產品
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    // 更新產品
    public Product updateProduct(Long id, Product productDetails) {
        Product product = productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found with id: " + id));
        
        product.setName(productDetails.getName());
        product.setPrice(productDetails.getPrice());
        product.setDescription(productDetails.getDescription());
        
        return productRepository.save(product);
    }

    // 刪除產品
    public void deleteProduct(Long id) {
        Product product = productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found with id: " + id));
        
        productRepository.delete(product);
    }
}

這邊就可以看到我們使用 JPA 所帶來的好處,已經直接提供給我們很多 ORM 包裝好的方法,像是 findById(), findAll(), save(), delete(),這些本該是自己要撰寫 Sql 才能和資料庫溝通的,但我們在 Repository 沒有定義任何方法就可以使用,除了一個 findByName 這個是我們自己定義的,但你也會發現這方法也沒進行任何處理,只是一個簡單明瞭的名稱就可以做到查詢,其實都是 JPA 幫我們處理好,他會自行匹配你定義好在 Entity 的個別欄位,所以當你寫 findBy{欄位名} 他就會自行匹配對應的 sql ( SELECT * FROM prodcuts WHERE {欄位名} = ‘{值}’; ),確實都不用寫到任何SQL就可以完成基本的操作。

這些慣例優於配置的用法其實還有很多,是你想要進行複雜查詢要自己寫的話要怎麼使用,後面會有進一步介紹。

Controller 建立接口接收請求及回傳結果

最後就是開端口接收請求及回傳 Service 的結果

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    // 新增產品
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.createProduct(product);
    }

    // 查詢單一產品
    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Optional<Product> product = productService.getProductById(id);
        return product.map(ResponseEntity::ok)
                      .orElseGet(() -> ResponseEntity.notFound().build());
    }

    // 查詢所有產品
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    // 更新產品
    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
        try {
            Product updatedProduct = productService.updateProduct(id, productDetails);
            return ResponseEntity.ok(updatedProduct);
        } catch (RuntimeException e) {
            return ResponseEntity.notFound().build();
        }
    }

    // 刪除產品
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        try {
            productService.deleteProduct(id);
            return ResponseEntity.noContent().build();
        } catch (RuntimeException e) {
            return ResponseEntity.notFound().build();
        }
    }
}

看完你會對於Spring Data JPA 有進一步的了解了,也大概知道其中的特色在哪,有太多太多細節再透過後面的章節慢慢介紹。


Ref:

相關文章也會同步更新我的部落格,有興趣也可以在裡面找其他的技術分享跟資訊。


上一篇
Day 8 - Spring Data JPA, JDBC Template, Mybatis
下一篇
Day 10 - Spring Data JPA (2)資料庫查詢應用
系列文
關於我和 Spring Boot 變成家人的那件事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言